/*-
 * Copyright (c) 2011,2012 Kai Wang
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "ld.h"
#include "ld_arch.h"
#include "i386.h"
#include "amd64.h"

ELFTC_VCSID("$Id: ld_arch.c 2515 2012-06-06 23:05:00Z kaiwang27 $");

#define	LD_DEFAULT_ARCH		"amd64"

static struct ld_arch *_get_arch_from_target(struct ld *ld, char *target);

void
ld_arch_init(struct ld *ld)
{
	char *end;
	char arch[MAX_ARCH_NAME_LEN + 1], target[MAX_TARGET_NAME_LEN + 1];
	size_t len;

	/*
	 * Register supported architectures.
	 */

	i386_register(ld);
	amd64_register(ld);

	/*
	 * Find out default arch for output object.
	 */

	if ((end = strrchr(ld->ld_progname, '-')) != NULL &&
	    end != ld->ld_progname) {
		len = end - ld->ld_progname + 1;
		if (len > MAX_TARGET_NAME_LEN)
			return;
		strncpy(target, ld->ld_progname, len);
		target[len] = '\0';
		ld->ld_arch = _get_arch_from_target(ld, target);
	}

	if (ld->ld_arch == NULL) {
		snprintf(arch, sizeof(arch), "%s", LD_DEFAULT_ARCH);
		ld->ld_arch = ld_arch_find(ld, arch);
		if (ld->ld_arch == NULL)
			ld_fatal(ld, "Internal: could not determine output"
			    " object architecture");
	}
}

void
ld_arch_set(struct ld *ld, char *arch)
{

	ld->ld_arch = ld_arch_find(ld, arch);
	if (ld->ld_arch == NULL)
		ld_fatal(ld, "arch '%s' is not supported", arch);
}

void
ld_arch_set_from_target(struct ld *ld)
{

	if (ld->ld_otgt != NULL) {
		ld->ld_arch = _get_arch_from_target(ld, ld->ld_otgt_name);
		if (ld->ld_arch == NULL)
			ld_fatal(ld, "target '%s' is not supported",
			    ld->ld_otgt_name);
	}
}

int
ld_arch_equal(struct ld_arch *a1, struct ld_arch *a2)
{

	assert(a1 != NULL && a2 != NULL);

	if (a1 == a2)
		return (1);

	if (a1->alias == a2 || a2->alias == a1)
		return (1);

	if (a1->alias != NULL && a1->alias == a2->alias)
		return (1);

	return (0);
}

void
ld_arch_verify(struct ld *ld, const char *name, int mach)
{
	struct ld_arch *la;
	struct ld_state *ls;

	assert(ld->ld_arch != NULL);
	ls = &ld->ld_state;

	if ((la = ld_arch_guess_arch_name(ld, mach)) == NULL)
		ld_fatal(ld, "%s: ELF object architecture %#x not supported",
		    name, mach);

	if (!ld_arch_equal(la, ld->ld_arch)) {
		ls->ls_arch_conflict = 1;
		if (ls->ls_rerun || !ls->ls_first_elf_object)
			ld_fatal(ld, "%s: ELF object architecture `%s' "
			    "conflicts with output object architecture `%s'",
			    name, la->name, ld->ld_arch->name);
		ld->ld_arch = la;
	}

	ls->ls_first_elf_object = 0;
}

struct ld_arch *
ld_arch_guess_arch_name(struct ld *ld, int mach)
{
	char arch[MAX_ARCH_NAME_LEN + 1];

	/* TODO: we should also consider elf class and endianess. */

	switch (mach) {
	case EM_386:
		snprintf(arch, sizeof(arch), "%s", "i386");
		break;
	case EM_ARM:
		snprintf(arch, sizeof(arch), "%s", "arm");
		break;
	case EM_MIPS:
	case EM_MIPS_RS3_LE:
		snprintf(arch, sizeof(arch), "%s", "mips");
		break;
	case EM_PPC:
	case EM_PPC64:
		snprintf(arch, sizeof(arch), "%s", "ppc");
		break;
	case EM_SPARC:
	case EM_SPARCV9:
		snprintf(arch, sizeof(arch), "%s", "sparc");
		break;
	case EM_X86_64:
		snprintf(arch, sizeof(arch), "%s", "amd64");
		break;
	default:
		return (NULL);
	}

	return (ld_arch_find(ld, arch));
}

static struct ld_arch *
_get_arch_from_target(struct ld *ld, char *target)
{
	struct ld_arch *la;
	char *begin, *end, name[MAX_TARGET_NAME_LEN + 1];

	if ((begin = strchr(target, '-')) == NULL) {
		la = ld_arch_find(ld, target);
		return (la);
	}
	la = ld_arch_find(ld, begin + 1);
	if (la != NULL)
		return (la);

	strncpy(name, begin + 1, sizeof(name) - 1);
	name[sizeof(name) - 1] = '\0';
	while ((end = strrchr(name, '-')) != NULL) {
		*end = '\0';
		la = ld_arch_find(ld, name);
		if (la != NULL)
			return (la);
	}

	return (NULL);
}

struct ld_arch *
ld_arch_find(struct ld *ld, char *arch)
{
	struct ld_arch *la;

	HASH_FIND_STR(ld->ld_arch_list, arch, la);

	return (la);
}
